//
//  MSAudioConversion.m
//  AVPlayer
//
//  Created by Michael Ge on 2019/1/2.
//  Copyright © 2019 Michael. All rights reserved.
//

#import "MSAudioConversion.h"

NSErrorDomain const MSAudioConversionErrorDomain = @"MSAudioConversionErrorDomain";

@interface MSAudioConversion()
@property (strong,nonatomic,readonly)NSDictionary* defaultPCMSettings;
@end

@implementation MSAudioConversion
{
    
    AVAsset *mAsset;
    AVAssetReader *mAssetReader;
    AVAssetReaderAudioMixOutput *mAssetReaderOutput;
    NSURL *mSourceUrl;
    
    AVAssetWriter *mAssetWrite;
    AVAssetWriterInput *mAssetWriteInput;
    
    dispatch_queue_t mConversionQueue;

    NSDictionary *mPCMSettings;
    
    BOOL mIsConverting;
    BOOL mCancelConversion;
}

-(instancetype)init
{
    self = [super init];
    
    AudioChannelLayout layout;
    memset(&layout,0,sizeof(AudioChannelLayout));
    layout.mChannelLayoutTag = kAudioChannelLayoutTag_Stereo; // 声音效果（立体声）
    
    mPCMSettings = @{
      AVFormatIDKey: @(kAudioFormatLinearPCM), //输出 PCM 数据
      AVSampleRateKey: @44100.0, //采样率
      AVNumberOfChannelsKey: @2, //声道数
      AVLinearPCMBitDepthKey: @16,//采样深度
      AVLinearPCMIsNonInterleaved: @NO, // 音频采样是否非交错
      AVLinearPCMIsFloatKey:@NO,    // 采样信号是否浮点数
      AVLinearPCMIsBigEndianKey:@NO,// 音频采用高位优先的记录格式
      AVChannelLayoutKey:[NSData dataWithBytes:&layout length:sizeof(AudioChannelLayout)]
      };
    
    return self;
}

+(MSAudioConversion*)conversionForWav:(NSURL*)url output:(NSString*)path error:(NSError * _Nullable * _Nullable)outError
{
    MSAudioConversion *Conversion = [MSAudioConversion new];
    
    [Conversion createReader:url error:outError];
    
    if (outError && *outError){
        return nil;
    }
    
    if (![Conversion checkOutputFile:path error:outError]){
        return nil;
    }
    
    [Conversion createWriter:path outputType:AVFileTypeWAVE
        outputSettings:Conversion.defaultPCMSettings
                 error:outError];
    
    if (outError && *outError){
        return nil;
    }
    
    return Conversion;
}

-(NSDictionary*)defaultPCMSettings
{
    return mPCMSettings;
}

-(AVFileType) outputFileType
{
    return mAssetWrite.outputFileType;
}
-(NSString*) outputFilePath
{
    return mAssetWrite.outputURL.path;
}

-(NSURL*) sourceUrl
{
    return mSourceUrl;
}

-(BOOL)start
{
    @synchronized (self) {
        if (mIsConverting){
            _error = [NSError errorWithDomain:MSAudioConversionErrorDomain
                                         code:MSACErrorConverting
                                     userInfo:@{NSLocalizedFailureReasonErrorKey:@"正在转换中，不能重新启动"}];
            return NO;
        }
        
        mIsConverting = YES;
    }

    if (![mAssetReader startReading]){
        _error = mAssetReader.error;
        return NO;
    }
    
    if (![mAssetWrite startWriting]){
        _error = mAssetWrite.error;
        return NO;
    }
    
    mCancelConversion = NO;
    mConversionQueue = dispatch_queue_create("conver queue", DISPATCH_QUEUE_SERIAL);
    
    id<MSAudioConversionDelegate> delegate = self.delegate;
    if ([delegate respondsToSelector:@selector(audioConversionStarted:)]){
        if ([NSThread currentThread].isMainThread){
            [delegate audioConversionStarted:self];
        }
        else{
            dispatch_sync(dispatch_get_main_queue(),^{
                [delegate audioConversionStarted:self];
            });
        }
    }
    
    //开启写入 Session
    AVAssetTrack *track = mAsset.tracks.firstObject;
    CMTime startTime = CMTimeMake(0, track.naturalTimeScale);
    [mAssetWrite startSessionAtSourceTime:startTime];
    
    __weak id __self = self;
    [mAssetWriteInput requestMediaDataWhenReadyOnQueue:mConversionQueue
                                            usingBlock:^{
                                                [__self converProcess];
                                            }];
    
    return YES;
}

-(BOOL)cancel
{
    @synchronized (self) {
        if (mIsConverting){
            mCancelConversion = YES;
            return YES;
        }
        else{
            return NO;
        }
    }
}

#pragma mark - 私有
-(void)createReader:(NSURL*)url error:(NSError * _Nullable * _Nullable)outError
{
    mAsset = [AVAsset assetWithURL:url];
    mAssetReader = [AVAssetReader assetReaderWithAsset:mAsset error:outError];
    
    if (mAssetReader == nil){
        mAsset = nil;
        return;
    }
    
    mAssetReaderOutput = [AVAssetReaderAudioMixOutput assetReaderAudioMixOutputWithAudioTracks:mAsset.tracks
                                                                                 audioSettings:mPCMSettings];
    
    if (![mAssetReader canAddOutput:mAssetReaderOutput]){
        //不能添加这个输出，因为输出已经存在。这里不可能出现这个问题，因为 mAssetReaderOutput 是新创建的。
        *outError = [NSError errorWithDomain:MSAudioConversionErrorDomain
                                        code:MSACErrorUnknown
                                    userInfo:@{NSLocalizedFailureReasonErrorKey:@"Reader 输出已经存在"}];
        return;
    }
    
    [mAssetReader addOutput:mAssetReaderOutput];
    
    mSourceUrl = url;
}

-(void)createWriter:(NSString*)path
         outputType:(AVFileType)type
     outputSettings:(NSDictionary*)settings
              error:(NSError * _Nullable * _Nullable)outError
{
    mAssetWrite = [AVAssetWriter assetWriterWithURL:[[NSURL alloc]initFileURLWithPath:path]
                                           fileType:type
                                              error:outError];
    mAssetWriteInput = [AVAssetWriterInput assetWriterInputWithMediaType:AVMediaTypeAudio
                                                          outputSettings:settings];
    //是否让媒体数据保持实时。在此不需要开启，如果输入源是从采集而来则需要开启。
    mAssetWriteInput.expectsMediaDataInRealTime = NO;
    if (![mAssetWrite canAddInput:mAssetWriteInput]){
        *outError = [NSError errorWithDomain:MSAudioConversionErrorDomain
                                        code:MSACErrorUnknown
                                    userInfo:@{NSLocalizedFailureReasonErrorKey:@"Writer 输入已经存在"}];
        return;
    }
    [mAssetWrite addInput:mAssetWriteInput];
}

-(BOOL)checkOutputFile:(NSString*)path error:(NSError * _Nullable * _Nullable)outError
{
    NSFileManager *manager = [NSFileManager defaultManager];
    BOOL isDirectory;
    
    if (![manager fileExistsAtPath:path isDirectory:&isDirectory]){
        //文件不存在，则确保其父目录存在
        NSString *directory = [path stringByDeletingLastPathComponent];
        return [manager createDirectoryAtPath:directory
                  withIntermediateDirectories:YES
                                   attributes:nil
                                        error:outError];
    }
    else{
        //文件已经存在，检查是否是目录。
        if (isDirectory){
            *outError = [NSError errorWithDomain:MSAudioConversionErrorDomain
                                            code:MSACErrorFileIsDirectory
                                        userInfo:@{NSLocalizedFailureReasonErrorKey:@"文件是一个目录"}];
            return NO;
        }
        else{
            //删除已经存的文件
            return [manager removeItemAtPath:path error:outError];
        }
    }
}

-(void)onFinish
{
    mConversionQueue = nil;
    
    @synchronized (self) {
        mIsConverting = NO;
    }
    
    id<MSAudioConversionDelegate> delegate = self.delegate;
    if (mCancelConversion){
        if ([delegate respondsToSelector:@selector(audioConversionCancelled:)]){
            if ([NSThread currentThread].isMainThread){
                [delegate audioConversionCancelled:self];
            }
            else{
                dispatch_sync(dispatch_get_main_queue(),^{
                    [delegate audioConversionCancelled:self];
                });
            }
        }
    }
    else{
        if ([delegate respondsToSelector:@selector(audioConversionFinised:)]){
            if ([NSThread currentThread].isMainThread){
                [delegate audioConversionFinised:self];
            }
            else{
                dispatch_sync(dispatch_get_main_queue(),^{
                    [delegate audioConversionFinised:self];
                });
            }
        }
    }
}

-(void)converProcess
{
    if (![mAssetWriteInput isReadyForMoreMediaData]){
        return;
    }
    
    CMSampleBufferRef buffer = [mAssetReaderOutput copyNextSampleBuffer];
    if (buffer && !mCancelConversion){
        [mAssetWriteInput appendSampleBuffer:buffer];
    }
    else{
        //文件读取完毕，结束文件写入。
        [mAssetWriteInput markAsFinished];
        [mAssetReader cancelReading];
        
        __weak id __self = self;
        [mAssetWrite finishWritingWithCompletionHandler:^{
            [__self onFinish];
        }];
    }
}

@end
